Decoding Nikon NEF Files

NEF is the file format Nikon cameras use to store RAW pictures. There is no actionable documentation on the format. This page collects what I have been piecing together from various sources -- usually incomplete blog posts or very vague forum posts -- and by reading the source codes of dcraw and libopenraw.

All of this was verified on images taken with my Nikon D40 and on sample NEF images I have found online, but I do not guarantee correctness. In fact, I am aware that there are additional NEF variants that are not covered here.

If you know any additional information that is not listed here, please reach out!

I hope to do more interesting and creative adventures in image manipulation in the future, however this article is fairly dry. Unless you are interested in parsing NEF, you might not get much out of it.

You have been warned!

Introduction

While it pretends to be a fancy special file format, in reality NEF files are mostly just TIFF with some custom tags. If you want to decode NEF, you should read up o TIFF first; You should know what IFDs and sub-IFDs are and how to access them, what dir-entry tags and types are and how to read them, how IFDs contain images, and so on. This article henceforth assumes the reader is familiar with TIFF.

The custom TIFF tags Nikon uses in NEF are known. Thanks to the maintainers of EXIF Tool for this wonderfully compiled list, which was immensely helpful.

General Structure

NEF files are TIFF files. They start with the normal TIFF magic, indicating byte order. NEF files contain one main IFD, which seems to always be at offset 8 -- but you should not hardcode that, just read the location of the main IFD the way TIFF dictates.

Note that you can not reliably know what information an IFD holds without parsing it. I recommend a parsing strategy that works with a stack of IFD offsets, parsing them in sequence, adding new offsets to the stack as needed, while guessing the IFD type based on the available tags.

Main IFD

The main IFD contains an uncompressed RGB thumbnail of the image.

The following direntries are of interest:

EXIF IFD

You can get a lot of neat metadata from the EXIF IFD. The EXIF TIFF tags are well documented.

The following direntries are of special interest here:

MakerNote

Rather than adding another (sub-)IFD as the makernote, Nikon instead decided to embed another full TIFF document.

At the makernote offset, you should find the null-terminated magic string "Nikon". After that come 32 bits which I suspect to hold a version tag, but so far I did not find any use to parsing that.

Afterwards, you should find another TIFF magic string. Careful: Since the endianness of a TIFF document depends on the magic ("MM" vs "II"), it is theoretically possible that this sub-TIFF has a different one than the outer TIFF, however I have not yet encountered such cursed files.

The makernote should be the main IFD of the sub-TIFF. All offsets inside it are relative to the sub-TIFF instead of global file offsets.

There might be a different makernote format for older Nikon cameras, however I have not yet looked at that.

Main IFD of MakerNote

The actual contents of the makernote can be accessed via its main IFD. All offsets read from here are relative to the sub-TIFF instead of global file offsets.

The following direntries are of interest:

Linearisation Table

At the linearisation table offset, you can read two unsigned bytes, henceforth referred to as version0 and version1.

According to dcraw, if version0 equals 0x49 you need to seek forward by 2110 now. I do not know why. Perhaps there is additional data we skip, but its purpose is unknown to me.

Now you can read four 16 bit unsigned integers, the vpred table, and another 16 bit unsigned integer, the curve data count.

There are multiple different curve types with different decoding strategies.

After decoding the curve, it may have a "tail" of repeating values, which needs to be pruned.

I am somewhat sure other curve types exist, however I have not encountered them yet. I suspect, based on some vague sources, that NEF variants that work completely differently also exist. I know that some NEF variants need a white balance table, which may be encrypted, however I have yet to properly look into those.

Decoding full curves

This curve type needs no decoding. Simply read as many unsigned 16 bit integers as the curve data count.

Decoding sparse curves

This curve type is not stored in the file in its complete form. It has gaps we need to fill in.

Calculate the maximum amount of elements in the curve using the sample depth (henceforth bps "bits per sample").

debug.assert(12 == bps or 14 == bps);
const max = 1 << bps;

Calculate the step size.

const step = max / (curve_data_count - 1);
debug.assert(0 != step);

Read as many unsigned 16 bit integers as indicated by the curve data count, spaced out by the step.

for (0..curve_data_count) |i|
    curve[i * step] = takeInt(u16);

To fill in the elements in between the values you have just read from the file, calculate the average of the two nearest values from the file, weighted by index distance.

for (0..max) |i|
    curve[i] = (curve[i - i % step] * (step - i % step) + curve[i - i % step + step] * (i % step)) / step;

You may be tempted to just do these calculations "on the fly" while decoding the pixel data, however based on how many pixels there are to decode it is very likely more efficient to calculate the entire curve in advance. Especially for parallel decoding of multiple images.

Raw Pixel Data IFD

You can identify the IFD holding the raw pixel data with the following tags:

These other tags are of interest:

According to one source, the raw pixels may be stored as a format called "JPEG lossless". There are multiple things called "JPEG lossless" or "lossless JPEG" or variations thereof, but this likely refers to the lossless compression scheme that was part of the very first JPEG specification. I am not currently willing to pay money to read the JPEG spec, so I am unable to confirm whether this actually holds true.

Either way, the raw pixel data is compressed twice. Once with the linearisation table, and another time with a huffman-like encoding.

Decoding the raw pixel data

Work in progress...